import { Injectable } from '@nestjs/common';
import { FindOptionsWhere, ObjectLiteral, Repository, FindOptionsOrder, FindManyOptions, FindOptionsSelect, DataSource, FindOptionsSelectByString } from 'typeorm';
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';

type Where<T> = FindOptionsWhere<T>[] | FindOptionsWhere<T>

@Injectable()
export class BaseService<T = ObjectLiteral> {
    protected dataSource:DataSource


    public primaryKey:(keyof T);
    protected repository:Repository<T>;

    protected aliasToOriginalFieldsMap:{[key in keyof T]?:string};

    public getRepository() {
        return this.repository
    }


    get aliasToOriginalFields() {
        if(!this.aliasToOriginalFieldsMap) {
            this.aliasToOriginalFieldsMap = {}
            this.repository.metadata.ownColumns.forEach(column => {
                this.aliasToOriginalFieldsMap[column.propertyName] = column.databaseName
            })
        }

        return this.aliasToOriginalFieldsMap
    }

    get metadata () {
        return this.repository.metadata
    }

    get tableName() {
        return this.repository.metadata.tableName
    }

    createQueryBuilder() {
        return this.repository.createQueryBuilder()
    }

    aliasToFields(fields:Array<keyof T>) :string{
        const aliasToOriginalFieldsMap = this.aliasToOriginalFields
        return fields.map((field) => `\`${aliasToOriginalFieldsMap[field]}\` as \`${field as string}\``).join(',')
    }

    createQueryRunner() {
        return this.dataSource.createQueryRunner()
    }


    query(query: string, parameters?: any[]) {
        return this.repository.query(query, parameters)
    }

    async count(where? : Where<T>) {
        let total = await this.repository.count({
            where : where
        })
        
        return total
    }

    async pagination(pageNo:number|string, pageSize:number|string, where? : Where<T>, select? : FindOptionsSelect<T> | FindOptionsSelectByString<T>, order? : FindOptionsOrder<T>) {
        pageSize = Number(pageSize)
        return this.find(where, select, (Number(pageNo) - 1) * pageSize, pageSize, order)
    }

    findOne(where? : Where<T>, select? : FindOptionsSelect<T> | FindOptionsSelectByString<T>, order? : FindOptionsOrder<T>) {
        return this.repository.findOne({
            select : select,
            where : where,
            order : order
        })
    }

    find(where? : Where<T>, select? : FindOptionsSelect<T> | FindOptionsSelectByString<T>, skip? : number, take? : number, order? : FindOptionsOrder<T>) {
        let options:FindManyOptions<T> = {
            
        }
        if(where) {
            options.where = where
        }
        if(select) {
            options.select = select
        }
        if(skip) {
            options.skip = skip
        }
        if(take) {
            options.take = take
        }
        if(order) {
            options.order = order
        }
        return this.repository.find(options)
    }

    async value(where : Where<T>, field:keyof T) {
        const res = await this.findOne(where, [field])
        return res ? res[field] : undefined
    }

    async isExistsByWhere(where : Where<T>) :Promise<boolean>{
        let res = await this.findOne(where, [this.primaryKey])
        return res ? true : false
    }

    async insert(data : QueryDeepPartialEntity<T> | QueryDeepPartialEntity<T>[]) :Promise<{affectedRows : number; rows : any}>{
        let result:{affectedRows : number; rows : any[]} = null
        try {
            let res = await this.repository.insert(data)
            if(res) {
                let generatedMaps = res.generatedMaps
                if(generatedMaps && generatedMaps.length > 0) {
                    result = {
                        affectedRows : generatedMaps.length,
                        rows : generatedMaps
                    }
                }
            }
        } catch (error) {
            console.log(error)
        }
        
        return result
    }

    async update(criteria: string | string[] | number | number[] | Date | Date[] | FindOptionsWhere<T>, partialEntity: QueryDeepPartialEntity<T>) : Promise<number> {
        try {
            let res = await this.repository.update(criteria, partialEntity)
            return res.affected
        } catch (error) {
            console.log(error)
        }

        return 0
    }

    async delete(criteria: string | string[] | number | number[] | Date | Date[] | FindOptionsWhere<T>) : Promise<number> {
        try {
            let res = await this.repository.delete(criteria)
            return res.affected
        } catch (error) {
            
        }

        return 0
    }
}
